package cn.org.rapid_framework.generator.provider.db.table.model;

import java.util.Collection;
import java.util.List;

import cn.org.rapid_framework.generator.GeneratorProperties;
import cn.org.rapid_framework.generator.provider.db.table.model.ForeignKey.ReferenceKey;
import cn.org.rapid_framework.generator.provider.db.table.model.util.ColumnHelper;
import cn.org.rapid_framework.generator.util.GLogger;
import cn.org.rapid_framework.generator.util.StringHelper;
import cn.org.rapid_framework.generator.util.TestDataGenerator;
import cn.org.rapid_framework.generator.util.typemapping.ActionScriptDataTypesUtils;
import cn.org.rapid_framework.generator.util.typemapping.DatabaseDataTypesUtils;
import cn.org.rapid_framework.generator.util.typemapping.JavaPrimitiveTypeMapping;
import cn.org.rapid_framework.generator.util.typemapping.JdbcType;

/**
 * 用于生成代码的Columb对象.对应数据库表column
 * 
 * @author badqiu
 * @email badqiu(a)gmail.com
 */
public class Column implements java.io.Serializable, Cloneable {
	/**
	 * Reference to the containing table
	 */
	private Table _table;

	/**
	 * The java.sql.Types type
	 */
	private int _sqlType;

	/**
	 * The sql typename. provided by JDBC driver
	 */
	private String _sqlTypeName;

	/**
	 * The name of the column
	 */
	private String _sqlName;

	/**
	 * True if the column is a primary key
	 */
	private boolean _isPk;

	/**
	 * True if the column is a foreign key
	 */
	private boolean _isFk;

	/**
	 * @todo-javadoc Describe the column
	 */
	private int _size;

	/**
	 * @todo-javadoc Describe the column
	 */
	private int _decimalDigits;

	/**
	 * True if the column is nullable
	 */
	private boolean _isNullable;

	/**
	 * True if the column is indexed
	 */
	private boolean _isIndexed;

	/**
	 * True if the column is unique
	 */
	private boolean _isUnique;

	/**
	 * Null if the DB reports no default value
	 */
	private String _defaultValue;

	/**
	 * The comments of column
	 */
	private String _remarks;

	private String displayName;
	private String referenceTable;
	private String referenceColumn;
	private String referenceDisplayColumn;
	private String referenceSelectionObject;
	

	/**
	 * @param table
	 * @param sqlType
	 * @param sqlTypeName
	 * @param sqlName
	 * @param size
	 * @param decimalDigits
	 * @param isPk
	 * @param isNullable
	 * @param isIndexed
	 * @param isUnique
	 * @param defaultValue
	 * @param remarks
	 */
	public Column(Table table, int sqlType, String sqlTypeName, String sqlName,
			int size, int decimalDigits, boolean isPk, boolean isNullable,
			boolean isIndexed, boolean isUnique, String defaultValue,
			String remarks) {
		if (sqlName == null)
			throw new NullPointerException();
		_table = table;
		_sqlType = sqlType;
		_sqlName = sqlName;
		_sqlTypeName = sqlTypeName;
		_size = size;
		_decimalDigits = decimalDigits;
		_isPk = isPk;
		_isNullable = isNullable;
		_isIndexed = isIndexed;
		_isUnique = isUnique;
		_defaultValue = defaultValue;
		_remarks = remarks;

		GLogger.trace(sqlName + " isPk -> " + _isPk);

		initOtherProperties();
	}

	public Column(Column c) {
		this(c.getTable(), c.getSqlType(), c.getSqlTypeName(), c.getSqlName(),
				c.getSize(), c.getDecimalDigits(), c.isPk(), c.isNullable(), c
						.isIndexed(), c.isUnique(), c.getDefaultValue(), c
						.getRemarks());
	}

	public Column() {
	}

	
	public String getDisplayName() {
		return displayName;
	}

	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}

	
	public String getReferenceTableLower(){
		return StringHelper.uncapitalize(getReferenceTable());
	}
	
	public boolean getIsEditable(){
		if("createDate".equals(this.getColumnNameLower()) || "createUserId".equals(this.getColumnNameLower()) ||
				"updateDate".equals(this.getColumnNameLower()) || "updateUserId".equals(this.getColumnNameLower())){
			return false;
		}
		return true;
	}
	public String getReferenceTable() {
		
		Collection<ForeignKey> coll = _table.getImportedKeys().getAssociatedTables().values();
		for(ForeignKey fk : coll){
			if(fk.getParentTable().getClassName().equals(_table.getClassName())){
				Collection<String> c2 = fk.getParentColumns().values();
				for(String o : c2){
					if(o.equals(_sqlName)){
						return fk.getSqlTable().getClassName();
					}
//					System.out.println("ForeignKey :" + fk);
				}
			}
//			System.out.println("ForeignKey :" + fk);
		}
		
		return referenceTable;
	}

	public void setReferenceTable(String referenceTable) {
		this.referenceTable = referenceTable;
	}
	
	public boolean getIsForeignKey(){
		String referenceTable = getReferenceTable();
		if(referenceTable == null || "".equals(referenceTable.trim())){
			return false;
		}
		return true;
	}

	public String getReferenceColumn() {
		String refColumns = "";
		Collection<ForeignKey> tables = _table.getImportedKeys().getAssociatedTables().values();
		for(ForeignKey fk:tables){
			if(fk.getParentTable().getClassName().equals(_table.getClassName())){
				Collection<String> c2 = fk.getParentColumns().values();
				for(String o : c2){
					if(o.equals(_sqlName)){
						Collection c = fk.getColumns().values();
						for(Object str:c)
						{
							refColumns += str + ",";
						}
						break;
					}
				}
			}
			
		}
		if(refColumns.length() >=1){
			refColumns = refColumns.substring(0,refColumns.length() - 1);
		}
		return refColumns;
	}

	
	public void setReferenceColumn(String referenceColumn) {
		this.referenceColumn = referenceColumn;
	}

	public String getReferenceColumnLower(){
		return StringHelper.uncapitalize(getReferenceColumn()); 
	}
	
	public String getReferenceDisplayColumn() {
		return referenceDisplayColumn;
	}

	public void setReferenceDisplayColumn(String referenceDisplayColumn) {
		this.referenceDisplayColumn = referenceDisplayColumn;
	}

	
	public String getReferenceSelectionObject() {
		return referenceSelectionObject;
	}
	
	public String getReferenceSelectionObjectLower() {
		return StringHelper.uncapitalize(referenceSelectionObject);
	}

	public void setReferenceSelectionObject(String referenceSelectionObject) {
		this.referenceSelectionObject = referenceSelectionObject;
	}

	/**
	 * Gets the SqlType attribute of the Column object
	 * 
	 * @return The SqlType value
	 */
	public int getSqlType() {
		return _sqlType;
	}

	/**
	 * Gets the Table attribute of the DbColumn object
	 * 
	 * @return The Table value
	 */
	public Table getTable() {
		return _table;
	}

	/**
	 * Gets the Size attribute of the DbColumn object
	 * 
	 * @return The Size value
	 */
	public int getSize() {
		return _size;
	}

	/**
	 * Gets the DecimalDigits attribute of the DbColumn object
	 * 
	 * @return The DecimalDigits value
	 */
	public int getDecimalDigits() {
		return _decimalDigits;
	}

	/**
	 * Gets the SqlTypeName attribute of the Column object
	 * 
	 * @return The SqlTypeName value
	 */
	public String getSqlTypeName() {
		return _sqlTypeName;
	}

	/**
	 * Gets the SqlName attribute of the Column object
	 * 
	 * @return The SqlName value
	 */
	public String getSqlName() {
		if (_sqlName == null)
			throw new NullPointerException();
		return _sqlName;
	}

	/**
	 * Gets the Pk attribute of the Column object
	 * 
	 * @return The Pk value
	 */
	public boolean isPk() {
		return _isPk;
	}

	/**
	 * Gets the Fk attribute of the Column object
	 * 
	 * @return The Fk value
	 */
	public boolean isFk() {
		return _isFk;
	}

	/**
	 * Gets the Nullable attribute of the Column object
	 * 
	 * @return The Nullable value
	 */
	public boolean isNullable() {
		return _isNullable;
	}

	/**
	 * Gets the Indexed attribute of the DbColumn object
	 * 
	 * @return The Indexed value
	 */
	public boolean isIndexed() {
		return _isIndexed;
	}

	/**
	 * Gets the Unique attribute of the DbColumn object
	 * 
	 * @return The Unique value
	 */
	public boolean isUnique() {
		return _isUnique;
	}

	/**
	 * Gets the DefaultValue attribute of the DbColumn object
	 * 
	 * @return The DefaultValue value
	 */
	public String getDefaultValue() {
		return _defaultValue;
	}

	/**
	 * 列的数据库备注
	 * 
	 * @return
	 */
	public String getRemarks() {
		return _remarks;
	}

	public void setUpdatable(boolean updatable) {
		this.updatable = updatable;
	}

	public void setInsertable(boolean insertable) {
		this.insertable = insertable;
	}

	public void setNullable(boolean v) {
		this._isNullable = v;
	}

	public void setUnique(boolean unique) {
		_isUnique = unique;
	}

	public void setPk(boolean v) {
		this._isPk = v;
	}

	/**
	 * Describe what the method does
	 * 
	 * @return Describe the return value
	 * @todo-javadoc Write javadocs for method
	 * @todo-javadoc Write javadocs for return value
	 */
	public int hashCode() {
		if (getTable() != null) {
			return (getTable().getSqlName() + "#" + getSqlName()).hashCode();
		} else {
			return (getSqlName()).hashCode();
		}
	}

	/**
	 * Describe what the method does
	 * 
	 * @param o
	 *            Describe what the parameter does
	 * @return Describe the return value
	 * @todo-javadoc Write javadocs for method
	 * @todo-javadoc Write javadocs for method parameter
	 * @todo-javadoc Write javadocs for return value
	 */
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o instanceof Column) {
			Column other = (Column) o;
			if (getSqlName().equals(other.getSqlName())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Describe what the method does
	 * 
	 * @return Describe the return value
	 * @todo-javadoc Write javadocs for method
	 * @todo-javadoc Write javadocs for return value
	 */
	public String toString() {
		return getSqlName();
	}

	public Object clone() {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			// ignore
			return null;
		}
	}

	/**
	 * Describe what the method does
	 * 
	 * @return Describe the return value
	 * @todo-javadoc Write javadocs for method
	 * @todo-javadoc Write javadocs for return value
	 */
	protected String prefsPrefix() {
		return "tables/" + getTable().getSqlName() + "/columns/" + getSqlName();
	}

	/**
	 * Sets the Pk attribute of the DbColumn object
	 * 
	 * @param flag
	 *            The new Pk value
	 */
	void setFk(boolean flag) {
		_isFk = flag;
	}

	public String getUnderscoreName() {
		return getSqlName().toLowerCase();
	}

	/**
	 * 根据列名，根据sqlName计算得出，示例值： BirthDate
	 **/
	public String getColumnName() {
		return columnName;
	}

	/**
	 * 第一个字母小写的columName,等价于: StringHelper.uncapitalize(getColumnName()),示例值:
	 * birthDate
	 **/
	public String getColumnNameFirstLower() {
		return StringHelper.uncapitalize(getColumnName());
	}

	/**
	 * 全部小写的columName,等价于: getColumnName().toLowerCase(),示例值: birthdate
	 **/
	public String getColumnNameLowerCase() {
		return getColumnName().toLowerCase();
	}

	/**
	 * 使用 getColumnNameFirstLower()替换
	 * 
	 * @deprecated use getColumnNameFirstLower() instead
	 */
	public String getColumnNameLower() {
		return getColumnNameFirstLower();
	}

	/**
	 * 得到 jdbcSqlType类型名称，示例值:VARCHAR,DECIMAL, 现Ibatis3使用该属性
	 */
	public String getJdbcSqlTypeName() {
		return getJdbcType();
	}

	/**
	 * 得到 jdbcSqlType类型名称，示例值:VARCHAR,DECIMAL, 现Ibatis3使用该属性
	 */
	public String getJdbcType() {
		String result = JdbcType.getJdbcSqlTypeName(getSqlType());
		return result;
	}

	/**
	 * 列的别名，等价于：getRemarks().isEmpty() ? getColumnNameFirstLower() :
	 * getRemarks()
	 * 
	 * <br />
	 * 示例值: birthDate
	 */
	public String getColumnAlias() {
		return columnAlias;
	}

	/**
	 * 列的常量名称
	 * 
	 * <br />
	 * 示例值: BIRTH_DATE
	 */
	public String getConstantName() {
		return StringHelper.toUnderscoreName(getColumnName()).toUpperCase();
	}

	/**
	 * 
	 * @deprecated
	 */
	public boolean getIsNotIdOrVersionField() {
		return !isPk();
	}

	/** 得到 rapid-validation的验证表达式: required min-value-800 */
	public String getValidateString() {
		return isNullable() ? getNoRequiredValidateString() : "required "
				+ getNoRequiredValidateString();
	}
	
	/** 得到JQuery validation的验证表达式 */
	public String getJQueryValidateString() {
		return isNullable() ? ColumnHelper.getJqueryValidation(this) : "required:true,"
				+ ColumnHelper.getJqueryValidation(this);
	}
	
	/**
	 * 
	 * 得到parsely的验证表达式
	 */
	public String getParslyValidateString(){
		return isNullable() ? ColumnHelper.getJqueryValidation(this) : "data-parsley-required "
				+ ColumnHelper.getParslyValidation(this);
	}

	/** 得到 rapid-validation的验证表达式: min-value-800 */
	public String getNoRequiredValidateString() {
		return ColumnHelper.getRapidValidation(this);
	}

	/**
	 * 得到JSR303 bean validation(Hibernate Validator)的验证表达式: @NotNull @Min(100)
	 * @Max(800)
	 */
	public String[] getHibernateValidatorConstraintNames() {
		return ColumnHelper
				.removeHibernateValidatorSpecialTags(getHibernateValidatorExprssion());
	}

	/**
	 * 得到JSR303 bean validation(Hibernate Validator)的验证表达式: @NotNull @Min(100)
	 * @Max(800)
	 */
	public String getHibernateValidatorExprssion() {
		if(getColumnNameLower().equals("createType")){
			System.out.println("");
		}
		return hibernateValidatorExprssion;
	}

	public void setHibernateValidatorExprssion(String v) {
		hibernateValidatorExprssion = v;
	}

	/** 列是否是String类型 */
	public boolean getIsStringColumn() {
		return DatabaseDataTypesUtils.isString(getJavaType());
	}

	/** 列是否是日期类型 */
	public boolean getIsDateTimeColumn() {
		return DatabaseDataTypesUtils.isDate(getJavaType());
	}
	
	public boolean getIsDateColumn() {
		if(javaType.endsWith("Date")) {
			return true;
		}
		return false;
	}
	
	public boolean getIsTimestampColumn() {
		if(javaType.endsWith("Timestamp") || javaType.endsWith("Time")) {
			return true;
		}
		return false;
	}

	/** 列是否是Number类型 */
	public boolean getIsNumberColumn() {
		return DatabaseDataTypesUtils.isFloatNumber(getJavaType())
				|| DatabaseDataTypesUtils.isIntegerNumber(getJavaType());
	}

	/** 检查是否包含某些关键字,关键字以逗号分隔 */
	public boolean contains(String keywords) {
		if (keywords == null)
			throw new IllegalArgumentException("'keywords' must be not null");
		return StringHelper.contains(getSqlName(), keywords.split(","));
	}

	public boolean isHtmlHidden() {
		return isPk() && _table.isSingleId();
	}
	
	public boolean getIsReferenceColumn(){
		return getReferenceColumn() != null && getReferenceColumn().length() > 1;
	}
	
	public boolean getIsSecondSelectionColumn(){
		return getIsReferenceColumn() && (referenceSelectionObject != null && referenceSelectionObject.length() > 1);
	}

	/**
	 * 得到对应的javaType,如java.lang.String,
	 * 
	 * @return
	 */
	public String getJavaType() {
		return javaType;
	}

	/**
	 * 得到简短的javaType的名称，如com.company.model.UserInfo,将返回 UserInfo
	 * 
	 * @return
	 */
	public String getSimpleJavaType() {
		return StringHelper.getJavaClassSimpleName(getJavaType());
	}

	/**
	 * 得到尽可能简短的javaType的名称，如果是java.lang.String,将返回String,
	 * 如com.company.model.UserInfo,将返回 com.company.model.UserInfo
	 * 
	 * @return
	 */
	public String getPossibleShortJavaType() {
		if (getJavaType().startsWith("java.lang.")) {
			return getSimpleJavaType();
		} else {
			return getJavaType();
		}
	}

	public boolean isPrimitive() {
		return JavaPrimitiveTypeMapping.getWrapperTypeOrNull(getJavaType()) != null;
	}

	/**
	 * 得到原生类型的javaType,如java.lang.Integer将返回int,而非原生类型,将直接返回getSimpleJavaType()
	 * 
	 * @return
	 */
	public String getPrimitiveJavaType() {
		return JavaPrimitiveTypeMapping.getPrimitiveType(getSimpleJavaType());
	}

	/** 得到ActionScript的映射类型,用于Flex代码的生成 */
	public String getAsType() {
		return asType;
	}

	/** 得到列的测试数据 */
	public String getTestData() {
		return new TestDataGenerator().getDBUnitTestData(getColumnName(),
				getJavaType(), getSize());
	}

	/** 列是否可以更新 */
	public boolean isUpdatable() {
		return updatable;
	}
	
	/**
	 * 是否classNameLower与sqlName一致
	 */
	public boolean isColumnNameLowerEqualSqlName(){
		if(getColumnNameLower().equals(getSqlName())){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * sqlname是否是id
	 */
	public boolean isSqlNameEqualId(){
		if(getSqlName().equals("id")){
			return true;
		}
		return false;
	}

	/** 列是否可以插入 */
	public boolean isInsertable() {
		return insertable;
	}

	/** 得到枚举(enum)的类名称，示例值：SexEnum */
	public String getEnumClassName() {
		return enumClassName;
	}

	/** 枚举值,以分号分隔,示例值:M(1,男);F(0,女) 或者是:M(男);F(女) */
	public void setEnumString(String str) {
		this.enumString = str;
	}

	/** 枚举值,以分号分隔,示例值:M(1,男);F(0,女) 或者是:M(男);F(女) */
	public String getEnumString() {
		return enumString;
	}

	/** 解析getEnumString()字符串转换为List<EnumMetaDada>对象 */
	public List<EnumMetaDada> getEnumList() {
		return StringHelper.string2EnumMetaData(getEnumString());
	}

	/** 是否是枚举列，等价于:return getEnumList() != null && !getEnumList().isEmpty() */
	public boolean isEnumColumn() {
		return getEnumList() != null && !getEnumList().isEmpty();
	}

	public void setJavaType(String javaType) {
		this.javaType = javaType;
	}

	public void setColumnAlias(String columnAlias) {
		this.columnAlias = columnAlias;
	}

	public void setColumnName(String columnName) {
		this.columnName = columnName;
	}

	public void setAsType(String asType) {
		this.asType = asType;
	}

	public void setEnumClassName(String enumClassName) {
		this.enumClassName = enumClassName;
	}

	// public void setBelongsTo(String foreignKey) {
	// ReferenceKey ref = ReferenceKey.fromString(foreignKey);
	// if(ref != null && _table != null) {
	// _table.getImportedKeys().addForeignKey(ref.tableName, ref.columnSqlName,
	// getSqlName(), ref.columnSqlName.hashCode());
	// }
	// }
	//
	// public void setHasAndBelongsToMany(String foreignKey) {
	// }

	private ReferenceKey hasOne;

	public String getHasOne() {
		return ReferenceKey.toString(hasOne);
	}

	/**
	 * nullValue for ibatis sqlmap: <result property="age" column="age"
	 * nullValue="0" />
	 */
	public String getNullValue() {
		return JavaPrimitiveTypeMapping.getDefaultValue(getJavaType());
	}

	public boolean isHasNullValue() {
		return JavaPrimitiveTypeMapping.getWrapperTypeOrNull(getJavaType()) != null;
	}

	/**
	 * 设置many-to-one,foreignKey格式: fk_table_name(fk_column) 或者
	 * schema_name.fk_table_name(fk_column)
	 * 
	 * @param foreignKey
	 * @return
	 */
	public void setHasOne(String foreignKey) {
		hasOne = ReferenceKey.fromString(foreignKey);
		if (hasOne != null && _table != null) {
			// Table refTable =
			// TableFactory.getInstance().getTable(hasOne.tableName);
			// _table.getImportedKeys().addForeignKey(refTable.getSqlName(),
			// hasOne.columnSqlName, getSqlName(),
			// hasOne.columnSqlName.toLowerCase().hashCode());
			_table.getImportedKeys().addForeignKey(hasOne.tableName,
					hasOne.columnSqlName, getSqlName(),
					hasOne.columnSqlName.toLowerCase().hashCode());
		}
	}

	private ReferenceKey hasMany = null;

	public String getHasMany() {
		return ReferenceKey.toString(hasMany);
	}

	/**
	 * 设置one-to-many,foreignKey格式: fk_table_name(fk_column) 或者
	 * schema_name.fk_table_name(fk_column)
	 * 
	 * @param foreignKey
	 * @return
	 */
	public void setHasMany(String foreignKey) {
		hasMany = ReferenceKey.fromString(foreignKey);
		if (hasMany != null && _table != null) {
			// Table refTable =
			// TableFactory.getInstance().getTable(hasMany.tableName);
			// _table.getExportedKeys().addForeignKey(refTable.getSqlName(),
			// hasMany.columnSqlName, getSqlName(),
			// hasMany.columnSqlName.toLowerCase().hashCode());
			_table.getExportedKeys().addForeignKey(hasMany.tableName,
					hasMany.columnSqlName, getSqlName(),
					hasMany.columnSqlName.toLowerCase().hashCode());
		}
	}

	private void initOtherProperties() {
		String normalJdbcJavaType = DatabaseDataTypesUtils
				.getPreferredJavaType(getSqlType(), getSize(),
						getDecimalDigits());
		javaType = GeneratorProperties.getProperty(
				"java_typemapping." + normalJdbcJavaType, normalJdbcJavaType)
				.trim();
		columnName = StringHelper.makeAllWordFirstLetterUpperCase(StringHelper
				.toUnderscoreName(getSqlName()));
		enumClassName = getColumnName() + "Enum";
		asType = ActionScriptDataTypesUtils.getPreferredAsType(getJavaType());
		columnAlias = StringHelper.removeCrlf(StringHelper.defaultIfEmpty(
				getRemarks(), getColumnNameFirstLower()));
		setHibernateValidatorExprssion(ColumnHelper
				.getHibernateValidatorExpression(this));
		displayName = StringHelper.removeCrlf(StringHelper.defaultIfEmpty(
				getRemarks(), getColumnNameFirstLower()));;
		referenceTable = "";
		referenceColumn = "";
		referenceDisplayColumn = "";
		referenceSelectionObject = "";
	}

	/**
	 * 删除聚集函数的相关char,示例转换 count(*) => count, max(age) => max_age, sum(income) =>
	 * sum_income
	 */
	public static String removeAggregationColumnChars(String columSqlName) {
		return columSqlName.replace('(', '_').replace(")", "").replace("*", "");
	}

	private String enumString = "";
	private String javaType;
	private String columnAlias;
	private String columnName;
	private String asType;
	private String enumClassName;
	private boolean updatable = true;
	private boolean insertable = true;
	private String hibernateValidatorExprssion;

	// private String rapidValidation;
	/**
	 * public enum ${enumClassName} { ${enumAlias}(${enumKey},${enumDesc});
	 * private String key; private String value; }
	 * 
	 * @author badqiu
	 */
	public static class EnumMetaDada {
		private String enumAlias;
		private String enumKey;
		private String enumDesc;

		public EnumMetaDada(String enumAlias, String enumKey, String enumDesc) {
			super();
			this.enumAlias = enumAlias;
			this.enumKey = enumKey;
			this.enumDesc = enumDesc;
		}

		public String getEnumAlias() {
			return enumAlias;
		}

		public String getEnumKey() {
			return enumKey;
		}

		public String getEnumDesc() {
			return enumDesc;
		}
	}
}
